'use strict';

const Velocity = require('velocityjs');
const isPlainObject = require('lodash').isPlainObject;

const debugLog = require('./debugLog');
const stringPollution = require('./javaHelpers');

const Compile = Velocity.Compile;
const parse = Velocity.parse;

function tryToParseJSON(string) {
  let parsed;
  try {
    parsed = JSON.parse(string);
  }
  catch (err) {
    // nothing! Some things are not meant to be parsed.
  }

  return parsed || string;
}

function renderVelocityString(velocityString, context) {

  // Add Java helpers to String prototype
  stringPollution.polluteStringPrototype();

  // This line can throw, but this function does not handle errors
  // Quick args explanation:
  // { escape: false } --> otherwise would escape &, < and > chars with html (&amp;, &lt; and &gt;)
  // render(context, null, true) --> null: no custom macros; true: silent mode, just like APIG
  const renderResult = (new Compile(parse(velocityString), { escape: false })).render(context, null, true);

  // Remove Java helpers from String prototype
  stringPollution.depolluteStringPrototype();

  debugLog('Velocity rendered:', renderResult || 'undefined');

  // Haaaa Velocity... this language sure loves strings a lot
  switch (renderResult) {

    case 'undefined':
      return undefined; // But we don't, we want JavaScript types

    case 'null':
      return null;

    case 'true':
      return true;

    case 'false':
      return false;

    default:
      return tryToParseJSON(renderResult);
  }
}

/*
  Deeply traverses a Serverless-style JSON (Velocity) template
  When it finds a string, assumes it's Velocity language and renders it.
*/
module.exports = function renderVelocityTemplateObject(templateObject, context) {

  const result = {};
  let toProcess = templateObject;

  // In some projects, the template object is a string, let us see if it's JSON
  if (typeof toProcess === 'string') toProcess = tryToParseJSON(toProcess);

  // Let's check again
  if (isPlainObject(toProcess)) {
    for (let key in toProcess) { // eslint-disable-line prefer-const

      const value = toProcess[key];
      debugLog('Processing key:', key, '- value:', value);

      if (typeof value === 'string') result[key] = renderVelocityString(value, context);

      // Go deeper
      else if (isPlainObject(value)) result[key] = renderVelocityTemplateObject(value, context);

      // This should never happen: value should either be a string or a plain object
      else result[key] = value;
    }
  }

  // Still a string? Maybe it's some complex Velocity stuff
  else if (typeof toProcess === 'string') {

    // If the plugin threw here then you should consider reviewing your template or posting an issue.
    const alternativeResult = tryToParseJSON(renderVelocityString(toProcess, context));

    return isPlainObject(alternativeResult) ? alternativeResult : result;
  }

  return result;
};
